feat: native shell completions for the CLI (#231) - #235
Merged
Conversation
Closes #231. `scope completions <SHELL>` prints a completion script to stdout for bash, zsh, fish, powershell or elvish, generated ahead of time by clap_complete, so `scope se` + Tab completes to `scope serial` through the shell's own completion engine. Verified by hand on every shell the issue asks for: bash 3.2.57 (the macOS system bash) and 5.3.9, zsh 5.9 (real Tab in a PTY, installed exactly as the README documents), fish 4.8.1 and PowerShell 7.5.4. Two constraints are not obvious and both fail silently, so both are pinned by tests. First, `Cli` needs an explicit `#[command(name = "scope")]`: clap_derive otherwise names the command after CARGO_PKG_NAME (`scope-monitor`), and the script registers a completion for a command nobody runs. Second, the arm is dispatched right after `Cli::parse()`, before the fallible-setup closure, and returns early — a completion script is evaluated on every shell start-up, so it must not read `config.toml` (one typo there would break the user's prompt rather than just scope) and must not reach the `See you later ^^` epilogue, which the shell would try to execute. With the arm inside the closure, three of the new tests fail, the zsh one included. Static generation on purpose, not the `unstable-dynamic` API: that feature is semver-exempt, its bash and fish hooks drop the filename fallback that `--tag-file` needs, and live port values would come from the USB-only `list::usb_ports` with no fallback. Completing live serial ports is a follow-up. The subcommands also gained the doc comments clap needs to describe them in `--help` and in the scripts, and the root command an `after_help` tip pointing at `scope completions --help` — the only hint installer users ever see, since no install path sets completions up for them. tests/completions.rs has two layers: portable assertions on the emitted script (runs on all three CI OSes, and is what guards the two constraints above) and real completion driven through bash, fish, zsh and PowerShell, each skipped when its shell is absent so no runner fails for want of a shell. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
The static completion scripts surface argument descriptions (zsh and fish show them next to each candidate), which exposed that `-c/--capacity`, `-t/--tag-file` and `-l/--latency` never had doc comments — they completed and helped with an empty description. Neither did any positional: `serial <PORT> <BAUDRATE>`, `rtt <TARGET> <CHANNEL_NUM>`, `ble <NAME_DEVICE> <MTU>` and `list --verbose`. Describe all of them, including where each value actually comes from: capacity and tag_file fall back to config.toml before their built-in default, tag_file is used verbatim (no `~`/`$VAR` expansion), latency is in microseconds and 0 yields instead of sleeping, and the RTT channel defaults to 0. Also fix CLAUDE.md, which documented --latency in milliseconds. Every polling loop treats it as microseconds (`plugin/engine.rs`, `graphics/headless.rs`, `graphics/graphics_task.rs`, `interfaces/serial_if.rs`) except `interfaces/rtt_if.rs::wait`, which uses `from_millis` — so with the default the RTT loop sleeps 1000x longer than every other. The README already said microseconds; the divergence in rtt_if is left alone here, since changing an interface's polling rate is not a documentation change. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #231.
scope completions <SHELL>prints a native completion script to stdout, soscope se+ Tab completes toscope serialusing the shell's own completion engine — nothing is drawn by the app.Generated ahead of time by
clap_completeforbash,zsh,fish,powershellandelvish.Verified by hand, per shell
The issue requires Windows (powershell), macOS and Linux (bash, zsh, fish). Each was driven for real, not inspected:
COMP_WORDS/COMP_CWORDCOMPREPLY=[serial]COMPREPLY=[serial]$fpathas_scopeexactly as the README documents, real Tabscope serialcomplete -C 'scope se'serial.ps1,TabExpansion2serialTwo silent traps, both pinned by tests
Clineeds an explicit#[command(name = "scope")].clap_deriveotherwise names the command afterCARGO_PKG_NAME(scope-monitor), so the script emits#compdef scope-monitorand Tab never fires. Removing the attribute failsevery_shell_emits_a_script_for_the_scope_binary.Cli::parse(), before the fallible-setup closure, and returns early. A completion script is evaluated on every shell start-up, so it must not readconfig.toml— one typo there would break the user's prompt rather than justscope— and must not reach theprintln!("See you later ^^")epilogue, which the shell would try to execute. Moving the arm inside the closure fails three tests, the real-zsh one included (the epilogue genuinely breaks completion, it is not a cosmetic issue).The
Commands::Completions { .. } => unreachable!()arm inside the closure exists only to keep the match exhaustive — so this placement cannot silently regress.Deliberately static, not dynamic
clap_complete'sunstable-dynamicAPI would allow completing values (live serial ports forscope serial <TAB>), but it is semver-exempt across two feature surfaces, its bash/fish hooks drop the filename fallback--tag-fileneeds, and live values would come from the USB-onlylist::usb_portswith no fallback — so/dev/ttyS0and socat PTYs would complete to nothing. Live value completion is a follow-up, not part of closing v0.6.0.Dependency cost:
clap_completeadds exactly one crate;clapis bumped to its4.5.20floor in the manifest.Tests
tests/completions.rs, 10 tests in two layers:scopeand neverscope-monitor; the script is the only thing on stdout; it covers every subcommand and global flag; an unknown or missing shell is an error with no partial script; and a malformedconfig.tomldoes not stop the script.skip:message, never a failure): bash, fish, zsh (PTY) and two PowerShell cases — including barescope <TAB>listing the subcommands, which is the case a broken hook fails first.CI covers bash + PowerShell on all three runners and zsh on macOS; fish skips there and is covered locally.
Docs
~/.bash_profile, not~/.bashrc, and never reads the drop-in completions dir under bash 3.2; zsh needsfpathextended beforecompinit), plus PowerShell's$PROFILEpaths and the 5.1Restrictedexecution policy.scope completions --helprepeats the install commands, so the README isn't needed on the target machine.scope --helpnow describes every subcommand (they had no doc comments before) and carries a tip pointing atscope completions --help.Known limitations
cargo install,curl|sh,irm|iex,.msi): they run one command and restart the shell.dist'sincludereaches the tarballs but not the.msi, and the installers copy only binaries — so shipping the scripts would help nobody on the advertised paths. Theafter_helptip is the mitigation.windows-latestjob running these tests is what closes that gap — worth watching on this PR's first run.bleis offered by Tab even though it is unimplemented (clap_complete emits hidden subcommands too); its description now says so.scope serial <TAB>falls back to filenames, andelvishis advertised but exercised nowhere.🤖 Generated with Claude Code